﻿// Loader.cs
// This program contains a class that can load files into memory.
// It now also contains a static utility method that allows the user to load files into memory
// without instantiating a new instance of this class

using System;
using System.Runtime.InteropServices;
using System.IO;

namespace armsim
{

    /// <summary>
    /// This class provides a mechanism for loading a program into memory.
    /// </summary>
    public class VirtualLoader
    {
        /// <summary>
        /// The number of bytes that should be in the memory
        /// </summary>
        private uint MemorySize { get; set; }

        /// <summary>
        /// The name of the file to be loaded into memory
        /// </summary>
        private String FileName { get; set; }

        /// <summary>
        /// The starting point of the program... (not used)
        /// </summary>
        private uint _programEntryPoint;


        /// <summary>
        /// Initializes a new Loader class with a given file and memory size
        /// </summary>
        /// <param name="file">Name/path of the file to load</param>
        /// <param name="memorySize">Size of the file to load</param>
        public VirtualLoader(String file, uint memorySize = 32768U)
        {
            MemorySize = memorySize;
            FileName = file;
        }

        /// <summary>
        /// Gets the current program's entry point.
        /// </summary>
        /// <returns>The entry point</returns>
        public uint GetProgramEntryPoint()
        {
            return _programEntryPoint;
        }


        /// <summary>
        /// Loads the ELF file into RAM
        /// </summary>
        /// <returns>The RAM the file has been loaded into</returns>
        public VirtualMemory Load()
        {
            // RAM!
            var ram = new VirtualMemory(MemorySize);
            Logger.LogEvent("Loader", "Opening " + FileName);

            using (var stream = new FileStream(FileName, FileMode.Open))
            {
                var elfHeader = new ELF_Header();
                var data = new byte[Marshal.SizeOf(elfHeader)];

                // Materialize the Elf Header
                stream.Read(data, 0, data.Length);
                elfHeader = ByteArrayToStructure<ELF_Header>(data);

                //
                _programEntryPoint = elfHeader.e_entry;
                //


                Logger.LogEvent("Loader", "Number of segments: " + elfHeader.e_phnum);
                Logger.LogEvent("Loader", "Program header offset: " + elfHeader.e_phoff);

                for (var i = 0; i < elfHeader.e_phnum; ++i)
                {
                    stream.Seek(elfHeader.e_phoff + (i * elfHeader.e_phentsize), SeekOrigin.Begin);
                    data = new byte[elfHeader.e_phentsize];
                    stream.Read(data, 0, elfHeader.e_phentsize);

                    // Materialize the Elf header entry
                    var elfHeaderEntry = ByteArrayToStructure<ELF_Header_Entry>(data);

                    stream.Seek(elfHeaderEntry.p_offset, SeekOrigin.Begin);
                    data = new byte[elfHeaderEntry.p_filesz];
                    stream.Read(data, 0, (int)elfHeaderEntry.p_filesz);

                    Logger.LogEvent("Loader", String.Format("Segment1 - Address = {0}, Offset = {1}, Size = {2}", elfHeaderEntry.p_vaddr, elfHeaderEntry.p_offset, elfHeaderEntry.p_filesz));

                    // Write the bytes to RAM
                    for (var j = 0; j < data.Length; ++j)
                    {
                        ram.WriteByte((uint)(elfHeaderEntry.p_vaddr + j), data[j]);
                    }

                }

            }

            return ram;

        }



        /// <summary>
        /// Loads an elf file into ram and returns the entry point
        /// </summary>
        /// <param name="fileName">the elf file to read from</param>
        /// <param name="ram">the ram to be read into</param>
        /// <returns>the program entry point</returns>
        public static uint Load(String fileName, VirtualMemory ram)
        {
            
            Logger.LogEvent("Loader", "Opening " + fileName);

            using (var stream = new FileStream(fileName, FileMode.Open))
            {
                var elfHeader = new ELF_Header();
                var data = new byte[Marshal.SizeOf(elfHeader)];

                // Materialize the Elf Header
                stream.Read(data, 0, data.Length);
                elfHeader = ByteArrayToStructure<ELF_Header>(data);

                Logger.LogEvent("Loader", "Number of segments: " + elfHeader.e_phnum);
                Logger.LogEvent("Loader", "Program header offset: " + elfHeader.e_phoff);

                for (var i = 0; i < elfHeader.e_phnum; ++i)
                {
                    stream.Seek(elfHeader.e_phoff + (i * elfHeader.e_phentsize), SeekOrigin.Begin);
                    data = new byte[elfHeader.e_phentsize];
                    stream.Read(data, 0, elfHeader.e_phentsize);

                    // Materialize the Elf header entry
                    var elfHeaderEntry = ByteArrayToStructure<ELF_Header_Entry>(data);

                    stream.Seek(elfHeaderEntry.p_offset, SeekOrigin.Begin);
                    data = new byte[elfHeaderEntry.p_filesz];
                    stream.Read(data, 0, (int)elfHeaderEntry.p_filesz);

                    Logger.LogEvent("Loader", String.Format("Segment1 - Address = {0}, Offset = {1}, Size = {2}", elfHeaderEntry.p_vaddr, elfHeaderEntry.p_offset, elfHeaderEntry.p_filesz));

                    // Write the bytes to RAM
                    for (var j = 0; j < data.Length; ++j)
                    {
                        ram.WriteByte((uint)(elfHeaderEntry.p_vaddr + j), data[j]);
                    }

                }

                return elfHeader.e_entry;
            }
  
        }



        /// <summary>
        /// Convert an array of bytes into a struct 
        /// </summary>
        /// <typeparam name="T">The struct you want to convert the bytes into</typeparam>
        /// <param name="bytes">The bytes that will form the struct</param>
        /// <returns>The specified struct that you wanted to create</returns>
        public static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
        {
            var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
            var stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),
                typeof(T));
            handle.Free();
            return stuff;
        }


        


    }
}
